#!/usr/bin/python

import sys, os, re
import cPickle 

class Book:
  def __init__(self, cat, title):
    self.title = title.lower().strip()
    self.category = cat
    self.location = None
    self.acquired = False

  def status(self):
    if self.acquired:
      return "Acquired"
    return "Missing"

  def isFound(self):
    return self.acquired

  def acquire(self, containerNum):
    self.acquired = True
    self.location = containerNum

  def forget(self):
    self.acquired = False
    self.location = None

  def match(self, query):
    query = re.sub("\s+", "\\s", query).strip().lower()
    pattern = ".*"+query+".*"
    return re.search(pattern, self.title)

  def __str__(self):
    words = re.split("\s+", self.title)
    for i in range(len(words)):
      words[i] = words[i][0].upper() + words[i][1:]
    return " ".join(words)
 
  def setLocation(self, containerNum):
    self.location = containerNum   

class SkyrimLibrary:
  def __init__(self, dataFile=(os.path.expanduser("~")+"/skyrimBooks/library.pkl")):
    self.rawData = dataFile

    if not os.path.isdir(os.path.expanduser("~")+"/skyrimBooks"):
      os.mkdir(os.path.expanduser("~")+"/skyrimBooks")

    while not os.path.isfile(self.rawData):
      print "Previous Skyrim Library Data not found."
      print "Please supply the path to the raw library data to continue: "
      print "Current Directory: "
      print os.listdir(os.getcwd())
      self.rawData = raw_input("Path: ")

    try: 
      with open(self.rawData, "rb") as pklFile:
        self.library = cPickle.load(pklFile)
        self.bookList = cPickle.load(pklFile)
    except cPickle.UnpicklingError:
      self.loadLibraryFromRaw()

    self.execCmd = {"1": self.commit,  
                    "2": self.searchTitle, 
                    "3": self.printMissingBookList,
	            "4": self.reportProgress,
                    "5": self.shutdown}

  def booksAcquiredByCat(self, cat):
    return len([book for book in self.library[cat] if book.isFound()])

  def reportProgress(self):
    totals = [(cat, len(books)) for cat, books in self.library.iteritems()]
    for total in sorted(totals, key=lambda x: x[0]):
      # category -- # acquired / total (percentage)
      found = self.booksAcquiredByCat(total[0])
      print "%s -- %s / %s (%0.3f%%)" % (total[0], found, total[1], found/total[1])

  def loadLibraryFromRaw(self):
    with open(self.rawData, "rb") as f:
      self.data = f.read()
      self.library = {}
      sections = self.data.split("\n\n")
      for section in sections: 
        lines = section.split("\n")
        category = lines[0] 
        self.library[category] = [Book(category, title) for title in lines[1:] if title]
      self.bookList = reduce(lambda l1, l2: l1 + l2, self.library.values()) 
    
  def printMissingBookList(self):
    for category in sorted(self.library, key=lambda x: x[0]):
      print "==== %s ====" % category
      for book in sorted(self.library[category], key=lambda x: x.title):
        if not book.isFound():
          print "- %s" % book

  def searchTitle(self):
    query = raw_input("Full or partial title> ")
    matches = self.searchLibrary(query, True)
    for match in matches:
      print str(match) + " -- " + match.status()

  def searchLibrary(self, query, includeFound=False):
    if includeFound:
      return filter(lambda book: book.match(query), self.bookList)
    return filter(lambda book: book.match(query) and not book.isFound(), self.bookList)

  def updateLibrary(self, bookTitle):
    bookTitle = bookTitle.lower().strip()
    if bookTitle in self.bookList:
      self.bookList.remove(bookTitle)
      for category in self.library:
        if bookTitle in self.library[category]:
          self.library[category].remove(bookTitle)
      print "%s removed from missing books list" % bookTitle.title()
    else: 
      print "Requested book (%s) not in library" % bookTitle.title()
    
  def shutdown(self):
    with open(os.path.expanduser("~")+"/skyrimBooks/library.pkl", "wb") as out:
      cPickle.dump(self.library, out)
      cPickle.dump(self.bookList, out)
    exit()

  def printMainMenu(self):
    print "\n=== Menu ==="
    print "1) commit book to library"
    print "2) search books (all books)" 
    print "3) print books not collected"
    print "4) print library stats"
    print "5) exit\n"

  def commit(self):
    cmd = "???" 
    print "Provide a full or partial title to commit to the library."
    print "Press ENTER when done adding books"
    cmd = raw_input("full or partial tite: ")
    while cmd != "":
      foundBooks = self.searchLibrary(cmd)   
      if len(foundBooks) == 0:
        print "Your title didn't match any missing books."
      else:
        print "One or more missing books matched your query."
        print "Which one did you mean? (Press ENTER or 1 when only 1 result)"
        for i in range(len(foundBooks)):
          print "%s) %s" % (i+1, foundBooks[i].title())
        bookNum = raw_input("# of correct book> ").strip()
        if not bookNum:
          bookNum = 1
        else:
          bookNum = int(bookNum)
        self.updateLibrary(foundBooks[bookNum-1])
      cmd = raw_input("full or partial title: ")
 
  def handleCommand(self):
    cmd = raw_input("#> ")
    if cmd in self.execCmd:
      self.execCmd[cmd]()
 
  def mainloop(self):
    while True:
      try: 
        self.printMainMenu() 
        self.handleCommand() 
      except KeyboardInterrupt: 
        cmd = raw_input("\nSave changes from this session? (y/n)> ")
        if cmd != "n":
          self.shutdown
        else:
          exit()
      except Exception, e:
        print " === CAPTURED TRACEBACE === \n"
        print e.message + "\n"
        print "\nReturning to main menu..."

def main():
  sl = SkyrimLibrary()
  sl.mainloop()

if __name__ == "__main__":
  main()

